/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package fr.inria.openmath.omapi.implementation;

/**
 *
 * @author Mr.Sam
 */
import java.io.*;
import java.math.BigInteger;
import fr.inria.openmath.omapi.*;

public class XMLPrinter implements StreamPrinter {

    OutputStream output;

    UTF7Codec utf7;

    int indent;

    int curColumn;


    private void indent() throws IOException {
        int c;

        if (indent != 0) {
            output.write('\n');
            for (c = 0; c < curColumn; c++) {
                output.write(' ');
            }
        }
    }

    private void indent(int incr) throws IOException {
        if (incr >= 0) {
            indent();
            curColumn += indent * incr;
        } else {
            curColumn += indent * incr;
            indent();
        }
    }

    private String printLongHexInvertEndianess(long l)
            throws NumberFormatException {
        char enc[] = new char[16], LSNib, MSNib;
        int i;

        for (i = 0; i < 8; i++) {
            // OPTIM precomputed array for encoding
            MSNib = (char) ((l & 0xf0) >> 4);
            LSNib = (char) (l & 0x0f);
            l = l >> 8;
            if (MSNib < (char) 10)
                enc[i * 2] = (char) (MSNib + '0');
            else
                enc[i * 2] = (char) (MSNib + 'a' - 10);
            if (LSNib < (char) 10)
                enc[i * 2 + 1] = (char) (LSNib + '0');
            else
                enc[i * 2 + 1] = (char) (LSNib + 'a' - 10);
        }
        return new String(enc);
    }


    public XMLPrinter (OutputStream output, int indent) {
        this.output = output;
        this.indent = indent;
        this.utf7 = new UTF7Codec();
    }

    public XMLPrinter (OutputStream output) {
        this(output, 2);
    }

    public XMLPrinter () {
        this(new BufferedOutputStream(new DataOutputStream(System.out)), 2);
    }

    /**
     * Set the number of spaces skiped at each indentation level.
     *
     * @param indent The number of spaces skiped at each indentation level.
     *   0 means no indent will be performed.
     *
     */
    public void setIndent(int indent) {
        this.indent = indent;
    }

    /**
     * Get the curent number of spaces skiped at each indentation level.
     *
     * @return The curent number of spaces skiped at each indentation level.
     */
    public int getIndent() {
        return this.indent;
    }

    /**
     * @see fr.inria.openmath.omapi.StreamPrinter#setOutput
     */
    public void setOutput(OutputStream output) throws OMException {
        this.output = output;
    }

    /**
     * @see fr.inria.openmath.omapi.StreamPrinter#getOutput
     */
    public OutputStream getOutput() {
        return this.output;
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginObject
     */
    public void putBeginObject() throws IOException {
        indent(+1);
        output.write("<OMOBJ>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndObject
     */
    public void putEndObject() throws IOException {
        indent(-1);
        output.write("</OMOBJ>".getBytes());
        output.flush();
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginApplication
     */
    public void putBeginApplication() throws IOException {
        indent(+1);
        output.write("<OMA>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndApplication
     */
    public void putEndApplication() throws IOException {
        indent(-1);
        output.write("</OMA>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginAttribution
     */
    public void putBeginAttribution() throws IOException {
        indent(+1);
        output.write("<OMATTR>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndAttribution
     */
    public void putEndAttribution() throws IOException {
        indent(-1);
        output.write("</OMATTR>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginAttributePairs
     */
    public void putBeginAttributePairs() throws IOException {
        indent(+1);
        output.write("<OMATP>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndAttributePairs
     */
    public void putEndAttributePairs() throws IOException {
        indent(-1);
        output.write("</OMATP>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginBind
     */
    public void putBeginBind() throws IOException {
        indent(+1);
        output.write("<OMBIND>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndBind
     */
    public void putEndBind() throws IOException {
        indent(-1);
        output.write("</OMBIND>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginBVars
     */
    public void putBeginBVars() throws IOException {
        indent(+1);
        output.write("<OMBVAR>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndBVars
     */
    public void putEndBVars() throws IOException {
        indent(-1);
        output.write("</OMBVAR>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putBeginError
     */
    public void putBeginError() throws IOException {
        indent(+1);
        output.write("<OME>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putEndError
     */
    public void putEndError() throws IOException {
        indent(-1);
        output.write("</OME>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putInteger
     */
    public void putInteger(int value) throws IOException {
        indent();
        output.write(("<OMI>" + value + "</OMI>").getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putInteger
     */
    public void putInteger(int value, int base) throws IOException {
        indent();
        switch (base) {
            case 16: 
                output.write(("<OMI>" + java.lang.Integer.toHexString(value) +
                        "</OMI>").getBytes());
                break;
            case 10: 
                output.write(("<OMI>" + value + "</OMI>").getBytes());
                break;
            default: 
                throw new IOException(
                        "unsuported base for int encoding (" + base + ").");
        }
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putInteger
     */
    public void putInteger(BigInteger value, int base) throws IOException {
        indent();
        if (base != 16)
            base = 10;                 // OM spec says hex or dec, thats all!
        output.write(("<OMI>" + value.toString(base) + "</OMI>").getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putInteger
     */
    public void putInteger(BigInteger value) throws IOException {
        putInteger(value, 10);
    }


    /**
     * @see fr.inria.openmath.omapi.Printer#putFloat
     */
    public void putFloat(double value, int base) throws IOException {
        String s;
        indent();
        switch (base) {
            case 10: 
                s = "<OMF dec=\"" + value + "\"/>";
                break;
            case 16: 
                s = "<OMF hex=\"" + printLongHexInvertEndianess(
                        java.lang.Double.doubleToLongBits(value)) + "\"/>";
                break;
            default: 
                throw new IOException(
                        "unsuported base for float encoding (" + base + ").");
        }
        output.write(s.getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putFloat
     */
    public void putFloat(double value) throws IOException {
        putFloat(value, 16);
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putString
     */
    public void putString(String value) throws IOException {
        indent();
        output.write(("<OMSTR>").getBytes());
        utf7.writeString(output, value);
        output.write(("</OMSTR>").getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putVariable
     */
    public void putVariable(String name) throws IOException {
        indent();
        output.write(("<OMV name=\"" + name + "\"/>").getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putByteArray
     */
    public void putByteArray(byte[] value) throws IOException {
        Base64Encoder benc = new Base64Encoder(value);
        indent();
        output.write("<OMB>".getBytes());
        output.write(benc.processString().getBytes());
        indent();
        output.write("</OMB>".getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putSymbol
     */
    public void putSymbol(Symbol symbol) throws IOException {
        indent();
        output.write(("<OMS cd=\"" + symbol.getCD() + "\" name=\"" +
                symbol.getName() + "\"/>").getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putComment
     */
    public void putComment(String comment) throws IOException {
        indent();
        output.write(("<!--" + comment + "-->").getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#putPInstruction
     */
    public void putPInstruction(String pi) throws IOException {
        indent();
        output.write(("<?" + pi + "?>").getBytes());
    }

    /**
     * @see fr.inria.openmath.omapi.Printer#endPrint
     */
    public void endPrint() throws IOException {
        output.write((byte) '\n');
        output.flush();
    }

}
